// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <cmath>
#include <deque>
#include <stdexcept>
#include <utility>

namespace coin2::base::window {


class IndexMovingWindow {
 public:
  explicit IndexMovingWindow(size_t window_size) : window_size_(window_size) {
  }

  void Push(double value) {
    if (window_.size() == window_size_) {
      sum_ -= window_.front();
      window_.pop_front();
    }
    sum_ += value;
    window_.push_back(value);
  }

  size_t size() { return window_.size(); }
  double sum() { return sum_; }
  double mean() {
    if (window_.empty()) {
      throw std::runtime_error("called mean() on empty window");
    } else {
      return sum_ / window_.size();
    }
  }

  void Clear() {
    sum_ = 0;
    window_.clear();
  }

 private:
  size_t window_size_ = 0;
  double sum_ = 0;
  std::deque<double> window_;
};

class SecondOrderIndexMovingWindow {
 public:
  explicit SecondOrderIndexMovingWindow(int64_t window_size) : window_size_(window_size) {
  }

  void Push(double value) {
    if (window_.size() == window_size_) {
      sum_ -= window_.front();
      sum_sq_ -= window_.front() * window_.front();
      window_.pop_front();
    }
    sum_ += value;
    sum_sq_ += value * value;
    window_.push_back(value);
  }

  double sum() { return sum_; }
  double mean() {
    if (window_.empty())
      throw std::runtime_error("called mean() on empty window");
    else
      return sum_ / window_.size();
  }

  double var() {
    if (window_.empty()) {
      throw std::runtime_error("called var() on empty window");
    } else {
      // E[X^2] - E[X]^2
      size_t n = window_.size();
      return (sum_sq_ - (sum_ * sum_ / n)) / n;
    }
  }

  double std() { return std::sqrt(var()); }

  void Clear() {
    sum_ = 0;
    sum_sq_ = 0;
    window_.clear();
  }

 private:
  int64_t window_size_ = 0;
  double sum_ = 0;
  double sum_sq_ = 0;
  std::deque<double> window_;
};

}  // namespace coin2::base::window
